﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

/* C# Symmetric key encryption (also known as secret-key encryption), is a cryptographic technique that uses a single secret key to encrypt and decrypt data. 
 * The methods within symmetric encryption are also known as “ciphers”, and process plain text using the secret key to generate encrypted data, called “cipher text”.
 * 
 * Symmetric algorithms are fast and are convenient when encrypting large amounts of data. 
 * Decrypting the data without the key can take an attacker hundreds of years (if a proper key length is used).
 * 
 * The disadvantage of this technique is that the secret key must be available to both the process that encrypts the data and the process that decrypts the data. 
 * This is a challenging problem because the key itself cannot be protected with symmetric encryption.
 * The secure way to exchange the symmetric key is to encrypt it asymmetrically.
 */
namespace VMCCryptoLib.SymmetricEncryption
{
	/// <summary>
	/// 
	/// </summary>
    public class RijndaelEcryption
    {
        #region --- Method to Encrypt and Decrypt a file into another file
        /// <summary>
        /// Method to encrypt a content of file then save that encrypted content into a new file
        /// </summary>
        /// <param name="fileToEncrypt">path of file to encrypt</param>
        /// <param name="destination">place of new file which saved a encrypted content</param>
        /// <param name="password">key to decrypt a encrypted file</param>
        /// <returns>true if encrypted success or false if encrypt proccessing is failed</returns>
        public static bool EncryptFile(string fileToEncrypt, string destination, string password)
        {
            bool rs = false;
            try
            {
                // Create a StreamFile object(s)
                FileStream fi = new FileStream(fileToEncrypt, FileMode.Open, FileAccess.Read);
                FileStream fo = new FileStream(destination, FileMode.OpenOrCreate, FileAccess.ReadWrite);

                // Then we are going to derive a Key and an IV from the
                // Password and create an algorithm 
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
                    new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                                0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

                Rijndael algo = Rijndael.Create();
                algo.Key = pdb.GetBytes(32);
                algo.IV = pdb.GetBytes(16);

                // Read the uncrypted file into fileData
                byte[] fileData = new byte[fi.Length]; // buffer
                fi.Read(fileData, 0, (int)fi.Length); // read file to encrypt's content 

                ICryptoTransform encryptor = algo.CreateEncryptor(); // Create a ICrypto tranfor object

                CryptoStream encryptStream = new CryptoStream(fo, encryptor, CryptoStreamMode.Write); // Create a the Crypto Steam object 
				encryptStream.Write(fileData, 0, fileData.Length);// Write content into Crypto stream

                // Close file handle
                encryptStream.Close();
                fi.Close();
                fo.Close();

                rs = true;
            }
            catch (Exception ex)
            {
                return rs;
                throw new Exception(ex.Message);
            }
            return rs;
        }

        // Decrypt a file and save content into another file
        public static bool DecryptFile(string fileToDecrypt, string decryptoFile, string password)
        {
            bool rs = false;
            try
            {
                // Create a Stream object
                FileStream inFile = new FileStream(fileToDecrypt, FileMode.Open, FileAccess.Read);
                FileStream outFile = new FileStream(decryptoFile, FileMode.OpenOrCreate, FileAccess.Write);

                // Then we are going to derive a Key and an IV from
                // the Password and create an algorithm 
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
                    new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
                                0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});
                Rijndael symAlgo = Rijndael.Create();
                symAlgo.Key = pdb.GetBytes(32);
                symAlgo.IV = pdb.GetBytes(16);

                // Create a ICrypto tranfor object
                ICryptoTransform decryptor = symAlgo.CreateDecryptor();

                // Create a Crypto Stream to pump data
                CryptoStream stream = new CryptoStream(outFile, decryptor, CryptoStreamMode.Write);

                int bufferlgenth = 4096;
                byte[] buffer = new byte[bufferlgenth];
                int byteReads;
                do
                {
                    byteReads = inFile.Read(buffer, 0, bufferlgenth);

                    // Decrypt file
                    stream.Write(buffer, 0, byteReads);

                } while (byteReads != 0);
                stream.Close();
                stream.Dispose();
                inFile.Close();
                inFile.Dispose();

                rs = true;
            }
            catch (Exception ex)
            {
                rs = false;
                throw new Exception(ex.Message);
            }
            return rs;
        }
        #endregion

        #region --- Method to Encrypt and Decrypt a string
        // Encrypt and return a string
        public static string EncryptString(string inputData, string password)
        {
            string rs = string.Empty;

            byte[] clearBytes = Encoding.Unicode.GetBytes(inputData);
            
            // Then, we need to turn the password into Key and IV 
            // We are using salt to make it harder to guess our key
            // using a dictionary attack - 
            // trying to guess a password by enumerating all possible words. 
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            // Using PasswordDeriveBytes object we are first getting
            // 32 bytes for the Key 
            // (the default Rijndael key length is 256bit = 32bytes)
            // and then 16 bytes for the IV. 
            // IV should always be the block size, which is by default
            // 16 bytes (128 bit) for Rijndael. 
            byte[] encryptData = RijndaelEncrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));
            rs = Convert.ToBase64String(encryptData);

            return rs;
        }

        // Decrypt and return a string
        public static string DecryptString(string cipherData, string password)
        {
            string rs = string.Empty;
            byte[] cipherBytes = Convert.FromBase64String(cipherData);

            PasswordDeriveBytes pdb = new PasswordDeriveBytes(password,
            new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 
                        0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            byte[] decryptData = RijndaelDecrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));

            rs = Encoding.Unicode.GetString(decryptData);
            return rs;
        }
        #endregion

        #region --- Private Method(s)
        // Encrypt a byte array into a byte array using a key and an IV 
        private static byte[] RijndaelEncrypt(byte[] clearData, byte[] Key, byte[] IV)
        {
            // Create a MemoryStream to accept the encrypted bytes 
            MemoryStream ms = new MemoryStream();

            // Create a symmetric algorithm. 
            // We are going to use Rijndael because it is strong and
            // available on all platforms. 
            // You can use other algorithms, to do so substitute the
            // next line with something like 
            // ripleDES alg = TripleDES.Create(); 
            Rijndael alg = Rijndael.Create();

            // Now set the key and the IV. 
            // We need the IV (Initialization Vector) because
            // the algorithm is operating in its default 
            // mode called CBC (Cipher Block Chaining).
            // The IV is XORed with the first block (8 byte) 
            // of the data before it is encrypted, and then each
            // encrypted block is XORed with the 
            // following block of plaintext.
            // This is done to make encryption more secure. 

            // There is also a mode called ECB which does not need an IV,
            // but it is much less secure. 
            alg.Key = Key;
            alg.IV = IV;

            // Create a CryptoStream through which we are going to be
            // pumping our data. 
            // CryptoStreamMode.Write means that we are going to be
            // writing data to the stream and the output will be written
            // in the MemoryStream we have provided. 
            CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);

            cs.Write(clearData, 0, clearData.Length); // Write the data and make it do the encryption 

            // Close the crypto stream (or do FlushFinalBlock). 
            // This will tell it that we have done our encryption and
            // there is no more data coming in, 
            // and it is now a good time to apply the padding and
            // finalize the encryption process. 
            cs.Close();

            // Now get the encrypted data from the MemoryStream.
            // Some people make a mistake of using GetBuffer() here,
            // which is not the right way. 
            byte[] encryptedData = ms.ToArray();

            return encryptedData;
        }

        // Decrypt a byte arrat into byte array using a key and an IV
        private static byte[] RijndaelDecrypt(byte[] cipherData, byte[] Key, byte[] IV)
        {
            // Create a MemoryStream that is going to accept the
            // decrypted bytes 
            MemoryStream ms = new MemoryStream();

            // Create a symmetric algorithm. 
            // We are going to use Rijndael because it is strong and
            // available on all platforms. 
            // You can use other algorithms, to do so substitute the next
            // line with something like 
            //     TripleDES alg = TripleDES.Create(); 
            Rijndael alg = Rijndael.Create();

            // Now set the key and the IV. 
            // We need the IV (Initialization Vector) because the algorithm
            // is operating in its default 
            // mode called CBC (Cipher Block Chaining). The IV is XORed with
            // the first block (8 byte) 
            // of the data after it is decrypted, and then each decrypted
            // block is XORed with the previous 
            // cipher block. This is done to make encryption more secure. 
            // There is also a mode called ECB which does not need an IV,
            // but it is much less secure. 
            alg.Key = Key;
            alg.IV = IV;

            // Create a CryptoStream through which we are going to be
            // pumping our data. 
            // CryptoStreamMode.Write means that we are going to be
            // writing data to the stream 
            // and the output will be written in the MemoryStream
            // we have provided. 
            CryptoStream cs = new CryptoStream(ms,
                							   alg.CreateDecryptor(),
                							   CryptoStreamMode.Write);

            // Write the data and make it do the decryption 
            cs.Write(cipherData, 0, cipherData.Length);

            // Close the crypto stream (or do FlushFinalBlock). 
            // This will tell it that we have done our decryption
            // and there is no more data coming in, 
            // and it is now a good time to remove the padding
            // and finalize the decryption process. 
            cs.Close();

            // Now get the decrypted data from the MemoryStream. 
            // Some people make a mistake of using GetBuffer() here,
            // which is not the right way. 
            byte[] decryptedData = ms.ToArray();

            return decryptedData;
        }
        #endregion
    }

}
